import { Vector3 } from "@babylonjs/core/Maths/math.vector";
import { AbstractDragLimit } from "./abstract-drag-limit";
import { TransformNode } from "@babylonjs/core/Meshes/transformNode";
import { Calculation, IRayClosestToSplineInfo, MIN_FLOAT } from "../../tool/calculation";
import { IDragProgressEventData, IDragEventData } from "../config/abstract-drag-config";
import { Camera } from "@babylonjs/core/Cameras/camera";

/**
 * 拖动中的回调函数
 * @param target 对象
 * @param manual 是否手动
 * @param t 控制参数
 */
interface IProgressCallback {
    (target: TransformNode, manual: boolean, t: number): void
}

/**
 * 约束在以给定点集所代表的多段线上拖动
 * @param vertices 代表多段线的顶点集合
 */
class DragSplineLimit extends AbstractDragLimit {

    private _vertices: Array<Vector3> = [];
    private _distances: Array<number> = [];
    private _t: number = Number.POSITIVE_INFINITY;
    private _origin: Vector3 = new Vector3();
    private _pointOnRay: Vector3 = new Vector3();
    private _pointOnLine: Vector3 = new Vector3();
    private _direction: Vector3 = new Vector3();
    private _pickOffset: Vector3 = new Vector3();

    constructor(vertices: Array<Vector3>) {
        super();
        this.setInformation(vertices);
    }

    /**
     * 获取约束类型标识
     */
    public get limitType(): string {
        return 'SplineLimit';
    }

    /**
     * 拖动中的回调函数
     * @param func (target:目标;manual:手动触发;t:控制参数):void
     */
    public set onUpdate(func: IProgressCallback) {
        this.updateCall = func;
    }

    /**
     * 设置拖动条件
     * @param vertices 代表多段线的点集
     */
    public setInformation(vertices: Array<Vector3>): void {
        this._vertices.length = 0;
        this._distances.length = 0;
        this._vertices.push(...vertices);
        Calculation.distancesOfPoints(this._vertices, this._distances);
    }

    /**
     * 用给定控制参数更新目标
     * @param target 目标
     * @param t 控制参数（起点到终点的区域0-1，不能超出范围）
     */
    public update(target: TransformNode, t: number): void {
        const ot = this._t;
        this._t = Math.max(Math.min(t, 1), 0);
        if (this._t === ot) return;

        const dis = this._distances;
        const totalLength = dis[dis.length - 1];
        const tarLen = Math.max(totalLength * t - MIN_FLOAT, MIN_FLOAT);
        let sIndex = -1, eIndex = -1;
        for (let i = 0; i < dis.length - 1; i++) {
            if (tarLen > dis[i] && tarLen <= dis[i + 1]) {
                sIndex = i;
                eIndex = i + 1;
            }
        }
        if (sIndex < 0) {
            console.warn('failed to update [SplineLimit]!', target);
            return;
        }
        const vertices = this._vertices;
        const rat = (tarLen - dis[sIndex]) / (dis[eIndex] - dis[sIndex]);
        const pos = this.currentPosition.copyFrom(vertices[eIndex]).subtractInPlace(vertices[sIndex]);
        pos.scaleInPlace(rat);
        pos.addInPlace(vertices[sIndex]);
        this.applyPositionToTarget(target);
        if (this.updateCall) this.updateCall(target, false, this._t);
    }

    /**
     * 提取控制参数（起点到终点的区域0-1，不能超出范围）
     * @returns 当前控制参数
     */
    public extract(): number {
        return this._t;
    }

    public setStart(target: TransformNode, eventData: IDragEventData): void {
        super.setStart(target, eventData);
        const camera = this.getActiveCamera(target.getScene());
        if (!camera) return;
        this._pickOffset.copyFrom(this.currentPosition).subtractInPlace(target.absolutePosition);
        this.startOffset.set(0, 0, 0);
    }

    public setProgress(target: TransformNode, eventData: IDragProgressEventData): void {
        super.setProgress(target, eventData);
        const camera = this.getActiveCamera(target.getScene());
        if (!camera) {
            return;
        }
        this.currentPosition.subtractInPlace(this._pickOffset);
        const closestPoint = this._pointOnLine;
        const closestInfo = this.computeClosestPoint(camera, closestPoint);
        this.currentPosition.copyFrom(closestPoint);

        const pre = closestInfo.previousIndex;
        const nxt = closestInfo.nextIndex;
        const d = Vector3.Distance(closestPoint, this._vertices[pre]);
        let tol = 0;
        const dis = this._distances;
        for (let i = 0; i < nxt; i++) {
            tol += dis[i];
        }
        this._t = (tol + d) / dis[dis.length - 1];
        if (this.updateCall) this.updateCall(target, true, this._t, eventData);
    }

    private computeClosestPoint(camera: Camera, pointOnLine: Vector3): IRayClosestToSplineInfo {
        const vertices = this._vertices;
        const cur = this.currentPosition;
        const origin = this._origin.copyFrom(camera.globalPosition);
        const direction = this._direction.copyFrom(cur).subtractInPlace(origin).normalize();
        const por = this._pointOnRay;
        return Calculation.closestPointOfRayAndSpline(origin, direction, vertices, por, pointOnLine);
    }

}


export { DragSplineLimit };